import { type Node as VFNode, Position as VFPosition, useVueFlow } from "@vue-flow/core";

type VFNodeType = 'input' | 'default' | 'output' | NonNullString;

const ids = ref<Record<VFNodeType, number>>({
  input: 0,
  default: 0,
  output: 0,
})
const idsTypeOffset: Record<VFNodeType, number> = {
  input: 0, // 0 - 159
  output: 1 << 4, // 160 - 319
  default: 1 << 5, // 320 - ??
}

export function getIdTypeOffset(id_number: number): VFNodeType {
  const res = id_number % 160
  if (res < 0) 
    throw new Error('unknown error at id number.')
  
  switch (res) {
    case 0:
      return 'input';
    case 1:
      return 'output';
    default:
      return 'default';
  }
}
/**
 * @description get a unique id.
 */
function getId(type: VFNodeType = 'default'): string {
  const id = ids.value[type]
  ids.value[type] += 1;
  return id.toString();
}
const theName = ref("");
/**
 * In a real world scenario you'd want to avoid creating refs in a global scope like this as they might not be cleaned up properly.
 * @description vue-flow拖拽的状态
 */
const vfState = {
  draggedType: ref<string>(),
  isDragOver: ref(false),
  isDragging: ref(false),
};

// TODO: i18n
export const nodeTypeMapping = {
  input: "开始",
  default: "知识点",
  output: "总结",
} as const;

function mkNodePosition(type: VFNodeType = 'default'): {
  targetPosition?: VFPosition
  sourcePosition?: VFPosition
} {
  if (type === 'input') {
    return {
      // 只有右边有连线点
      // targetPosition: Position.Left,
      sourcePosition: VFPosition.Right,
    };
  }
  else if (type === 'output') {
    return {
      // 只有左边有连线点
      targetPosition: VFPosition.Left,
      // sourcePosition: Position.Right,
    }
  }
  else {
    return {
      targetPosition: VFPosition.Left,
      sourcePosition: VFPosition.Right,
    }
  }

  // targetPosition: Position.Left,
  // sourcePosition: Position.Right,
}

/**
 * @description 拖拽和放置的主逻辑
 */
export const useDragAndDrop = () => {
  const { draggedType, isDragOver, isDragging } = vfState;

  const {
    addNodes,
    screenToFlowCoordinate,
    onNodesInitialized,
    updateNode,
    $reset,
  } = useVueFlow();

  watch(isDragging, (dragging) => {
    document.body.style.userSelect = dragging ? "none" : "";
  });

  function onDragStart(event: DragEvent, type: VFNodeType, targetName: string) {
    theName.value = targetName;
    if (event.dataTransfer) {
      event.dataTransfer.setData("application/vueflow", type);
      event.dataTransfer.effectAllowed = "move";
    }

    draggedType.value = type;
    isDragging.value = true;

    document.addEventListener("drop", onDragEnd);
  }

  /**
   * Handles the drag over event.
   *
   * @param {DragEvent} event
   */
  function onDragOver(event: DragEvent) {
    event.preventDefault();

    if (draggedType.value) {
      isDragOver.value = true;

      if (event.dataTransfer)
        event.dataTransfer.dropEffect = "move";
    }
  }

  function onDragLeave() {
    isDragOver.value = false;
  }

  function onDragEnd() {
    isDragging.value = false;
    isDragOver.value = false;
    draggedType.value = undefined;
    document.removeEventListener("drop", onDragEnd);
  }

  /**
   * Handles the drop event.
   *
   * @param {DragEvent} event
   */
  function onDrop(event: DragEvent) {
    const position = screenToFlowCoordinate({
      x: event.clientX,
      y: event.clientY,
    });
    const type = draggedType.value ?? 'default'
    const nodeId = getId(type);
    const label = `${theName.value}#${nodeId}`;

    // id 添加 node类型的offset
    const mappingId = idsTypeOffset[type].toString() + nodeId;
    const { targetPosition, sourcePosition } = mkNodePosition(type);
    const newNode: VFNode = {
      id: mappingId,
      type,
      data: { toolbarPosition: VFPosition.Right, toolbarVisible: true },
      position,
      label,
      targetPosition,
      sourcePosition,
      class: tw`!w-[100px]`.join(' '),
    };

    /**
     * Align node position after drop, so it's centered to the mouse
     *
     * We can hook into events even in a callback, and we can remove the event listener after it's been called.
     */
    const { off } = onNodesInitialized(() => {
      updateNode(nodeId, node => ({
        position: {
          x: node.position.x - node.dimensions.width / 2,
          y: node.position.y - node.dimensions.height / 2,
        },
      }));

      off();
    });

    addNodes(newNode);
  }
  function reset() {
    $reset();
    ids.value = {
      input: 0,
      default: 0,
      output: 0,
    };
  }
  function reload(idName: number | undefined) {
    ids.value.default = idName ?? 0;
  }
  return {
    draggedType,
    isDragOver,
    isDragging,
    onDragStart,
    onDragLeave,
    onDragOver,
    onDrop,
    reset,
    reload,
  };
}
